package com.tzy.spi.aop;

import com.tzy.spi.aop.Interceptor.*;
import com.tzy.spi.aop.annotition.*;
import com.tzy.spi.core.SpiLoader;
import com.tzy.spi.enhancer.Enhancer;
import com.tzy.spi.lifecycle.AfterInject;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;


//aop 增强器实现
public class AopEnhancer implements Enhancer, AfterInject {

    private ConcurrentHashMap<String, Map<String, MethodInterceptor>> aspectMap = new ConcurrentHashMap<>(64);
    private ConcurrentHashMap<String, AopBeanProxy> cache = new ConcurrentHashMap<>(64);

    @Override
    public <T> T enhanceBeforeInit(Class<T> type, T bean, String name) {
        return bean;
    }

    @Override
    public <T> T enhanceAfterInit(Class<T> type, T bean, String name) {

        AopBeanProxy aopBeanProxy = null;
        if (!cache.containsKey(name)) {
            HashMap<String, ArrayList<AspectHolder>> methodAspectMapping = new HashMap<>();
            //对传入的bean进行增强
            Method[] methods = bean.getClass().getMethods();
            for (Method method : methods) {
                Annotation[] annotations = method.getAnnotations();
                if (annotations.length != 0) {
                    //构建 方法和对应 所有拦截器的映射
                    ArrayList<AspectHolder> aspectHolders = buildMethodMapping(annotations);

                    methodAspectMapping.put(method.getName(), aspectHolders);
                }
            }

            aopBeanProxy = new AopBeanProxy(methodAspectMapping, bean);
            cache.put(name, aopBeanProxy);
        } else {
            aopBeanProxy = cache.get(name);
        }

        return (T) Proxy.newProxyInstance(
                SpiLoader.getClassLoader(),
                new Class[]{type},
                aopBeanProxy);
    }

    private ArrayList<AspectHolder> buildMethodMapping(Annotation[] annotations) {


        ArrayList<AspectHolder> aspectList = new ArrayList<>();
        for (Annotation annotation : annotations) {
            String name = annotation.annotationType().getName();
            Map<String, MethodInterceptor> map = aspectMap.get(name);

            if (map != null) {
                //将 注解 一并封装
                aspectList.add(new AspectHolder(annotation,map));
            }

        }

        return aspectList;
    }

    @Override
    public void afterInjectToDo() {

//        new ConcurrentHashMap<String,>()
        //初始化所有  切面
        SpiLoader.loadInterface(Aspect.class).initAllBeans();

        Map<String, Aspect> beans = SpiLoader.loadInterface(Aspect.class).getNativeBeans();

        //解析 所有切面
        for (Map.Entry<String, Aspect> aspectEntry : beans.entrySet()) {

            Aspect aspect = aspectEntry.getValue();
            parseAspect(aspect);
        }

    }

    private void parseAspect(Aspect aspect) {
        Class<? extends Aspect> clazz = aspect.getClass();
        Class aspectAnnotationClass = aspect.getAspectAnnotation();

        if (!Annotation.class.isAssignableFrom(aspectAnnotationClass)) {
            throw new RuntimeException(aspectAnnotationClass + "不是注解实现");
        }

        String name = aspectAnnotationClass.getName();

        HashMap<String, MethodInterceptor> map = new HashMap<>();

        for (Method method : clazz.getMethods()) {


            MethodInterceptor interceptor = null;

            if (method.getAnnotation(Before.class)!=null) {
                interceptor = new BeforeMethodInterceptor();
                map.put("before", interceptor);
            } else if (method.getAnnotation(After.class)!=null) {
                interceptor = new AfterMethodInterceptor();
                map.put("after", interceptor);
            } else if (method.getAnnotation(AfterReturning.class)!=null) {
                interceptor = new AfterReturningMethodInterceptor();
                map.put("afterReturning", interceptor);
            } else if (method.getAnnotation(AfterThrowing.class)!=null) {
                interceptor = new AfterThrowingMethodInterceptor();

                map.put("afterThrowing", interceptor);
            } else if (method.getAnnotation(Around.class)!=null) {
                interceptor = new AroundMethodInterceptor();
                map.put("around", interceptor);
            }

            if (interceptor != null) {
                interceptor.setAspect(aspect);
                interceptor.setMethod(method);
            }


        }

        aspectMap.put(name, map);
    }


    static class AspectHolder{
        private Annotation annotation;

        private Map<String, MethodInterceptor> map;


        public AspectHolder(Annotation annotation, Map<String, MethodInterceptor> map) {
            this.annotation = annotation;
            this.map = map;
        }

        public Annotation getAnnotation() {
            return annotation;
        }

        public Map<String, MethodInterceptor> getMap() {
            return map;
        }
    }

}
